import { checkPointInRectangle, invertMatrix, Matrix, matrixAppendMatrix, Point, pointAppendMatrix, Rectangle } from "./math";

export class Behaviour {


    displayObject: DisplayObject

    onStart() {

    }

    onUpdate(duringTime: number) {

    }

    onEnd() {

    }
}

export class AbstractRenderer extends Behaviour {

    onUpdate() {

    }
}

class BitmapRenderer extends Behaviour {

    source: string;

    onUpdate() {

    }
}

export class DisplayObject {

    behaviours: Behaviour[] = [];
    children: DisplayObject[] = [];

    onClick: Function;
    x = 0;
    y = 0;
    scaleX = 1;
    scaleY = 1;
    rotation = 0;
    alpha = 1;
    globalAlpha = 1;
    parent: DisplayObject | null = null;
    globalMatrix: Matrix;
    localMatrix: Matrix;
    bounds: Rectangle = new Rectangle();

    addChild(child: DisplayObject) {
        this.children.push(child);
        child.parent = this;
    }
    removeChild(child: DisplayObject) {
        const index = this.children.indexOf(child);
        if (index >= 0) {
            this.children.splice(index, 1);
        }
    }

    draw(context: CanvasRenderingContext2D) {

        if (this.parent) {
            this.globalAlpha = this.parent.globalAlpha * this.alpha;
        }
        else {
            this.globalAlpha = this.alpha;
        }
        context.globalAlpha = this.globalAlpha;


        const localMatrix = new Matrix();
        localMatrix.updateFromDisplayObject(this.x, this.y, this.scaleX, this.scaleY, this.rotation);
        this.localMatrix = localMatrix;
        let globalMatrix: Matrix;

        if (this.parent) {
            const parentGlobalMatrix = this.parent.globalMatrix;
            globalMatrix = matrixAppendMatrix(localMatrix, parentGlobalMatrix);
        } else {
            globalMatrix = localMatrix;
        }
        this.globalMatrix = globalMatrix;

        context.setTransform(globalMatrix.a, globalMatrix.b, globalMatrix.c, globalMatrix.d, globalMatrix.tx, globalMatrix.ty);
        this.render(context);
        for (const displayobject of this.children) {
            displayobject.draw(context);
        }
    }

    addBehaviour(behaviour: Behaviour) {
        this.behaviours.push(behaviour);
        behaviour.displayObject = this;
        behaviour.onStart();
    }

    getBehaviour<T extends typeof Behaviour>(clz: T): InstanceType<T> {
        for (const behaviour of this.behaviours) {
            const b = behaviour as any;
            if ((behaviour as any).constructor === clz) {
                return behaviour as any;
            }
        }
        return null;
    }

    removeBehaviour(behaviour: Behaviour) {
        const index = this.behaviours.indexOf(behaviour);
        if (index >= 0) {
            this.behaviours.splice(index, 0);
        }
    }

    onUpdate(duringTime: number) {
        for (const behaviour of this.behaviours) {
            behaviour.onUpdate(duringTime);
        }
        for (const child of this.children) {
            child.onUpdate(duringTime);
        }
    }

    hitTest(localThisPoint: Point): DisplayObject | null {
        for (let i = this.children.length - 1; i >= 0; i--) {
            const child = this.children[i];
            const childRelativeThisLocalMatrix = child.localMatrix;
            const invertChildRelativeThisLocalMatrix = invertMatrix(childRelativeThisLocalMatrix);
            const childLocalPoint = pointAppendMatrix(localThisPoint, invertChildRelativeThisLocalMatrix);
            const result = child.hitTest(childLocalPoint);
            if (result) {
                return result;
            }
        }
        return null;
    }

    render(context: CanvasRenderingContext2D): void {

    }
}

export class Bitmap extends DisplayObject {

    source: string;

    constructor() {
        super();
    }

    render(context: CanvasRenderingContext2D) {
        const img = getImage(this.source);
        if (img) {
            context.drawImage(img, 0, 0);
        }
    }

    hitTest(point: Point) {
        const img = getImage(this.source);
        if (img) {
            this.bounds.width = img.width;
            this.bounds.height = img.height;
        }
        if (checkPointInRectangle(point, this.bounds)) {
            return this;
        }
        else {
            return null;
        }
    }
}

export class RectangleShape extends DisplayObject {

    color = '#000000'
    width = 100;
    height = 100;

    render(context: CanvasRenderingContext2D) {
        context.save();
        context.fillStyle = this.color;
        context.fillRect(0, 0, this.width, this.height);
        context.restore();
    }

    hitTest(point: Point) {
        this.bounds.width = this.width;
        this.bounds.height = this.height;
        if (checkPointInRectangle(point, this.bounds)) {
            return this;
        }
        else {
            return null;
        }
    }
}


export class TextField extends DisplayObject {


    color = '#000000'
    text = '';

    render(context: CanvasRenderingContext2D) {
        context.save();
        context.fillStyle = this.color;
        context.fillText(this.text, 0, 20);
        context.restore();
    }

    hitTest(point: Point) {
        this.bounds.width = 100;
        this.bounds.height = 20;
        if (checkPointInRectangle(point, this.bounds)) {
            return this;
        }
        else {
            return null;
        }
    }
}

const store: any = {};



function getImage(url: string) {
    return store[url];
}

function loadMultiImage(list: string[], onSuccess: Function) {
    let count = 0;
    for (const url of list) {
        loadImage(url, function onLoadItem(img) {
            count++;
            store[url] = img;
            if (count === list.length) {
                onSuccess();
            }
        })
    }
}

function loadImage(url: string, onSuccess: Function) {
    const img = new Image();
    img.onload = function () {
        onSuccess(img);
    }
    //异步过程
    img.src = url;
}


export class GameEngine {
    objects: { [id: string]: DisplayObject } = {};

    private rootDisplayObject: DisplayObject;



    onStart?: () => void;
    onUpdate?: (duringTime: number) => void;
    onTick?: () => void;

    getHitTestResult(point: Point) {
        const result = this.rootDisplayObject.hitTest(point);
        console.log(result);
        return result;
    }

    private handleOnClick(obj: DisplayObject) {
        if (obj && obj.onClick) {
            obj.onClick();
        }
        dispatchParentOnClick(obj)
        function dispatchParentOnClick(obj: DisplayObject) {
            if (obj.parent) {
                if (obj.parent.onClick) {
                    obj.parent.onClick();
                }
                dispatchParentOnClick(obj.parent)
            }

        }
    }

    start(images: string[], sceneConfig: any, onComplete?: Function) {
        const initialTime = Date.now();
        loadMultiImage(images, () => {
            this.loadScene(sceneConfig);

            const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
            canvas.addEventListener('mousedown', (e) => {
                const point = new Point(e.offsetX, e.offsetY);
                const obj = this.getHitTestResult(point);
                this.handleOnClick(obj);
            })
            if (this.onStart) {
                this.onStart();
            }
            const advancedTime = Date.now() - initialTime;
            this.enterFrame(advancedTime);
            if (onComplete) {
                onComplete();
            }


        });
    }

    private lastTime = 0;

    /**
     * 心跳控制器
     */
    enterFrame(advancedTime: number) {
        const duringTime = advancedTime - this.lastTime;
        this.lastTime = advancedTime;
        this.onEnterFrame(duringTime);
        requestAnimationFrame((advancedTime) => this.enterFrame(advancedTime));
    }


    private iterationCurrentTime = 0;
    /**
     * 在每一帧执行 onTick，其中包含如下内容：
     * 1. 清空屏幕
     * 2. 执行业务逻辑
     * 3. 执行渲染
     */
    private onEnterFrame(duringTime: number) {
        const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
        const context = canvas.getContext('2d');
        context.save();
        context.clearRect(0, 0, canvas.width, canvas.height);

        this.iterationCurrentTime += duringTime;
        const fps = 60;
        const mileSecondPerTick = 1000 / fps;
        while (this.iterationCurrentTime >= mileSecondPerTick) {
            this.iterationCurrentTime -= mileSecondPerTick;
            if (this.onTick) {
                this.onTick();
            }
        }
        this.rootDisplayObject.onUpdate(duringTime);
        this.draw(context);
        context.restore();
    }

    getDisplayObject(id: string) {
        return this.objects[id];
    }


    draw(context: CanvasRenderingContext2D) {
        this.rootDisplayObject.draw(context);
    }

    loadScene(sceneConfig: any) {
        const object = this.createDisplayObject(sceneConfig.type, sceneConfig.properties, sceneConfig.children);
        this.rootDisplayObject = object;
    }

    private createDisplayObject(type: string, properties: any, children?: any[], id?: string): DisplayObject {
        let displayObject: DisplayObject;
        switch (type) {
            case 'rectangle':
                const rect = new RectangleShape();
                rect.color = properties.color || '0x000000';
                displayObject = rect;
                break;
            case 'textfield':
                const textfield = new TextField();
                textfield.color = properties.color || '0x000000';
                textfield.text = properties.text || '';
                displayObject = textfield;
                break;
            case 'bitmap':
                const bitmap = new Bitmap();
                const source = properties.source || '';
                // const bitmapRenderer = bitmap.getBehaviour(BitmapRenderer);
                // console.log(bitmapRenderer)
                bitmap.source = source
                displayObject = bitmap;
                break;
            case 'displayobject':
                const container = new DisplayObject();
                if (children) {
                    for (const child of children) {
                        const childDisplayObject = this.createDisplayObject(child.type, child.properties, child.children, child.id);
                        container.addChild(childDisplayObject);
                    }
                }
                displayObject = container;
                break;

        }
        displayObject.x = properties.x || 0;
        displayObject.y = properties.y || 0;
        displayObject.rotation = properties.rotation || 0;
        displayObject.scaleX = properties.hasOwnProperty('scaleX') ? properties.scaleX : 1;
        displayObject.scaleY = properties.hasOwnProperty('scaleY') ? properties.scaleY : 1;
        displayObject.alpha = properties.hasOwnProperty('alpha') ? properties.alpha : 1;

        if (id) {
            this.objects[id] = displayObject;
        }
        return displayObject;
    }


}

